package com.example.test.resolver;

import com.example.test.annotation.RequestView;
import com.example.test.exception.ResourceNotFoundException;
import com.example.test.utils.SpringUtils;
import com.example.test.utils.StringUtils;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.BeanUtils;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.core.MethodParameter;
import org.springframework.data.repository.CrudRepository;
import org.springframework.util.Assert;
import org.springframework.validation.BindException;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

import javax.servlet.ServletRequest;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 过滤请求参数的解析器
 */
@Log4j2
public class RequestViewArgumentResolver implements HandlerMethodArgumentResolver {
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(RequestView.class) && !BeanUtils.isSimpleProperty(parameter.getParameterType());
    }

    @Cacheable("requestViewDisallowFieldsCache")
    public String[] getDisallowFields(Class<?> clazz, RequestView requestView){
        Set<String> allowedFields = Stream.of(requestView.allowedFields()).collect(Collectors.toSet());
        Set<String> disallowedFields = Stream.of(requestView.disallowedFields()).collect(Collectors.toCollection(HashSet::new));
        Set<Class> groups = Stream.of(requestView.value()).collect(Collectors.toSet());
        if(!groups.isEmpty() || !allowedFields.isEmpty()) {
            PropertyDescriptor[] propertyDescriptors = BeanUtils.getPropertyDescriptors(clazz);
            for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
                try {
                    String fieldName = propertyDescriptor.getName();
                    if (allowedFields.contains(fieldName)) {
                        continue;
                    }
                    RequestView fieldRequestView;
                    Method writeMethod = propertyDescriptor.getWriteMethod();
                    if (writeMethod == null) continue;
                    fieldRequestView = writeMethod.getAnnotation(RequestView.class);
                    if (fieldRequestView == null) {
                        Field field = propertyDescriptor.getReadMethod().getDeclaringClass().getDeclaredField(propertyDescriptor.getName());
                        fieldRequestView = field.getAnnotation(RequestView.class);
                        if (fieldRequestView == null) {
                            disallowedFields.add(fieldName);
                            continue;
                        }
                    }
                    boolean flag = false;
                    for (Class c : fieldRequestView.value()) {
                        if (groups.contains(c)) {
                            flag = true;
                            break;
                        }
                    }
                    if (!flag) disallowedFields.add(fieldName);
                } catch (NoSuchFieldException e) {
                    log.error("绑定参数时参数无法找到", e);
                }
            }
        }
        return disallowedFields.toArray(new String[]{});
    }

    @Override
    public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
        RequestView requestView = methodParameter.getParameterAnnotation(RequestView.class);
        Assert.notNull(requestView, "参数绑定过滤时，注解获取失败");
        //过滤属性列表
        Class<?> paramType = methodParameter.getParameterType();
        Object target;

        if(requestView.merge()) {
            String idStr = nativeWebRequest.getParameter("id");
            if(StringUtils.isEmpty(idStr)){
                throw new RuntimeException("合并操作的请求参数中路径参数id为空");
            }
            long id;
            try{
                id = Long.parseLong(idStr);
            }catch (Exception e){
                log.error("pathValue ID 不是一个数字", e);
                throw new ResourceNotFoundException();
            }
            //noinspection unchecked
            CrudRepository<Object, Long> repository =
                    (CrudRepository<Object, Long>) SpringUtils.getParameterizedBean(CrudRepository.class, paramType, Long.class);
            if(repository == null) throw new RuntimeException("无法获取DAO: beanClass=" + paramType);
            target = repository.findById(id).orElseThrow(ResourceNotFoundException::new);
        }else{
            target = BeanUtils.instantiateClass(paramType);
        }

        ServletRequestDataBinder binder = (ServletRequestDataBinder) webDataBinderFactory.createBinder(nativeWebRequest, target, methodParameter.getParameterName());

        binder.setDisallowedFields(getDisallowFields(paramType, requestView));

        binder.bind(Objects.requireNonNull(nativeWebRequest.getNativeRequest(ServletRequest.class)));
        if(binder.getBindingResult().hasErrors()){
            throw new BindException(binder.getBindingResult());
        }
        return target;
    }
}
